機械学習モデルBodyPixを使ってAmazon Chime SDKのビデオ会議の背景をぼかしてみた(後編)
こんにちは、CX事業本部の若槻です。
前編の記事では、Amazon Chime SDKのビデオ会議の背景ぼかしをBodyPixによって行えることを簡単に確認しました。
今回の後編では、もう少し実用的な実装として、BodyPixによる「背景ぼかし機能」をデモアプリに追加してみました。
アウトプット
AWS公式が提供しているAmazon Chime SDKのデモアプリのローカルデモに「背景ぼかし機能」を追加しました。
映像入力メニューのドロップダウンリストに追加された[Bokeh BackGround]を選択すると、カメラ映像の背景をぼかすことができるようにしました。
背景ぼかし機能を使用している様子です。通常のカメラ映像の入力では人物の背景が鮮明に見えていますが、背景ぼかし機能を使うことにより背景のみぼかせています。
ソースコードはGitHubにアップロードしてあります。AWSの元のRepositoryをフォークして、今回の変更のブランチを切っています。
背景ぼかし機能(デモアプリ)の使い方
次のコマンドを実行し、GitHubからソースコードをcloneして今回の変更のブランチをcheckoutします。
% git clone https://github.com/cm-rwakatsuki/amazon-chime-sdk-js.git % git fetch origin implementing-video-background-bluring-with-bodypix % git checkout implementing-video-background-bluring-with-bodypix
AWSの認証情報の設定がまだの場合は設定してください。(今回使用する/demos/browser
のデモアプリはローカルで動作するため、バックエンドの構築は必要ありませんが、会議利用時にアプリが会議の作成、削除、出席者の作成をするためにAmazon Chime APIにアクセスするため、その際にAWSの認証情報が必要となります。)
次のコマンドを実行し、demos/browser
配下のデモアプリをローカルで起動します。合わせて依存関係のインストールも行われます。
% cd demos/browser % npm run start
アプリの起動に成功したら、ブラウザでhttp://127.0.0.1:8080/
にアクセスします。
会議参加画面でタイトルが開きます。参加者名を指定し、[Continue]をクリックします。
デバイス準備画面が開きます。[Join]をクリックして会議に参加します。
会議画面が開きます。カメラボタンをクリックして映像入力を開始し、ドロップダウンリストから[Bokeh BackGround]を選択すると、カメラ映像の背景をぼかすことができます。
実装の変更の解説
今回の機能を実現するために元のソースコードに対して行った変更について解説します。変更内容自体は下記のComparingページからも確認できます。
BodyPixの利用に必要なパッケージの導入
ブラウザでBodyPixを利用する場合は、下記の4つのパッケージのインストールが必要となります。@tensorflow-models/body-pix
がBodyPix本体、他の3つはJavaScriptでBodyPix(TensorFlow)を使用する場合に必要となります。
{ "dependencies": { "@tensorflow-models/body-pix": "^2.0.5", "@tensorflow/tfjs": "^2.7.0", "@tensorflow/tfjs-converter": "^2.7.0", "@tensorflow/tfjs-core": "^2.7.0", //other packages }, //other definitions }
またデモアプリのJavaScriptコードとなるmeetingV2.ts
に以下の記述を追加し、前述のパッケージがアプリ上で使用できるようにします。ただし@tensorflow/tfjs
はimportは必要ですが処理内で明示的に指定しての使用はしません。よってunuseエラー抑制のためにconsole.log
でログ出力のためだけに指定するようにしています。
import * as bodyPix from '@tensorflow-models/body-pix'; import * as tf from '@tensorflow/tfjs'; console.log('Using TensorFlow backend: ', tf.getBackend());
SourceMapのロード失敗エラーの抑制
BodyPixを導入したデモアプリを起動したところコンソールで下記のようなエラーが継続的に出力されるようになりました。
DevTools failed to load SourceMap: Could not load content for webpack://app_meetingV2/node_modules/@tensorflow/tfjs/dist/version.js.map: HTTP error: status code 404, net::ERR_UNKNOWN_URL_SCHEME
次の記事によるとこのエラーはSourceMap Loaderを導入することにより抑制できるとのことです。
記事に従いdevDependenciesとしてsource-map-loader
を追加し、webpack.config.js
に記述を追加したらエラーを抑制できました。
{ "devDependencies": { "source-map-loader": "^1.1.2", //other packages }, //other definitions }
module.exports = env => { return { module: { rules: [ { test: /\.js$/, enforce: 'pre', use: ['source-map-loader'], }, //other rules ], }, //other definitions }; };
背景ぼかし処理
今回、背景ぼかし処理および処理映像のビデオタイルへの出力は次のようにして実現させています。
- カメラで撮影した映像をHTMLVideoElementに描画する
- HTMLVideoElementに描画された動画に対してBodyPixで背景ぼかし処理を行う
- ぼかし処理した映像をHTMLCanvasElementに描画する
- HTMLCanvasElementから取得したMediaStreamを
chooseVideoInputDevice()
(ビデオタイルに出力する映像・音声を指定するAmazon Chime SDKのメソッド)の入力とする
1、2、3の処理は次の3つのメソッドで行います。
setupCamera()
:メディアデバイス(カメラのみ)による入力処理を開始segmentBody()
:HTMLVideoElementから取得した映像に対し、BodyPixによる人物領域の取得、人物の背景のボケ加工を行い、HTMLCanvasElementに描画する処理を連続的に行うstartDrawBokehEffect()
:HTMLVideoElementの作成、HTMLCanvasElementの作成とDOMへの追加(追加しない場合は処理が正常に動作しませんでした)、setupCamera()
およびstartDrawBokehEffect()
の開始を行う
async setupCamera(videoElement: any) { const stream = await navigator.mediaDevices.getUserMedia({ video: { width: 1280, height: 720, }, audio: false, }); videoElement.srcObject = stream; return new Promise(resolve => { videoElement.onloadedmetadata = () => { videoElement.play(); resolve(); }; }); } segmentBody(input: HTMLVideoElement, output: HTMLCanvasElement, bodypixnet: bodyPix.BodyPix) { async function renderFrame() { const segmentation = await bodypixnet.segmentPerson(input); const backgroundBlurAmount = 7; const edgeBlurAmount = 3; const flipHorizontal = false; bodyPix.drawBokehEffect( output, input, segmentation, backgroundBlurAmount, edgeBlurAmount, flipHorizontal ); if (document.getElementById('blurredCanvasElement')) { requestAnimationFrame(renderFrame); } } renderFrame(); } async startDrawBokehEffect() { const input = document.createElement('video') as HTMLVideoElement; input.width = 1280; input.height = 720; input.muted = true; input.autoplay = true; input.setAttribute('id', 'inputVideoElement'); if (input.srcObject) { input.srcObject = null; } else { const output = document.createElement('canvas') as HTMLCanvasElement; output.width = 1280; output.height = 720; output.setAttribute('id', 'blurredCanvasElement'); output.setAttribute('style', 'display:none'); document.body.appendChild(output); await this.setupCamera(input); const bodypixnet: bodyPix.BodyPix = await bodyPix.load(); this.segmentBody(input, output, bodypixnet); } } }
本部分の実装は下記ブログの内容をとても参考にさせて頂きました。
- ビデオ会議の背景をぼかせる!BodyPixを使った背景ぼかし加工 | さくらのナレッジ
- Amazon Chime SDKとBodyPixでビデオ会議システムにバーチャル背景を実装した話。 - フレクトのクラウドblog re:newal
背景ぼかし処理とビデオタイルへの入力の開始トリガー
ドロップダウンリストのメニューを定義するpopulateVideoInputList()
で、カメラ映像の背景ぼかしを開始するメニューとしてBokeh BackGround
を追加します。
async populateVideoInputList(): Promise<void> { const genericName = 'Camera'; const additionalDevices = ['None', 'Blue', 'SMPTE Color Bars', 'Bokeh BackGround']; //other processes
videoInputSelectionToDevice()
メソッドはドロップダウンリストで選択されたメニューに従い、画像、メディアデバイスIDまたはMediaStreamが返すメソッドです。Bokeh BackGround
が選択されると、startDrawBokehEffect()
を実行してHTMLCanvasElementへの背景ぼかし映像の描画を開始し、HTMLCanvasElementからcaptureStream()
によりMediaStreamを取得して戻り値とするようにしています。このメソッドの戻り値は呼び出し元でchooseVideoInputDevice()
に渡されます。
private videoInputSelectionToDevice(value: string): Device { if (this.isRecorder() || this.isBroadcaster()) { return null; } if (value === 'Blue') { return DefaultDeviceController.synthesizeVideoDevice('blue'); } else if (value === 'SMPTE Color Bars') { return DefaultDeviceController.synthesizeVideoDevice('smpte'); } else if (value === 'None') { return null; } else if (value === 'Bokeh BackGround') { this.startDrawBokehEffect(); interface CanvasElement extends HTMLCanvasElement { captureStream(frameRate?: number): MediaStream; } return (<CanvasElement>document.getElementById('blurredCanvasElement')).captureStream(); } return value; }
中間要素の削除
映像出力が選択され直された際に中間要素(HTMLVideoElement、HTMLCanvasElement)を削除するようにしています。segmentBody()
ではinputVideoElementが存在しない場合にぼかし処理・結果の描画を停止するようにしています。
removeIntermediateElement(): void { const blurredCanvasElement = document.getElementById('blurredCanvasElement'); if (blurredCanvasElement) { blurredCanvasElement.remove(); console.log('blurredCanvasElement removed'); } const inputVideoElement = document.getElementById('inputVideoElement'); if (inputVideoElement) { inputVideoElement.remove(); console.log('inputVideoElement removed'); } }
const buttonVideo = document.getElementById('button-camera'); buttonVideo.addEventListener('click', _e => { this.removeIntermediateElement(); //other processes
async openVideoInputFromSelection(selection: string | null, showPreview: boolean): Promise<void> { this.removeIntermediateElement(); //other processes
おわりに
BodyPixによる「背景ぼかし機能」をAmazon Chime SDKのデモアプリに追加してみました
今回の実装を行うにあたりAmazon Chime SDkだけでなくHTML5でのメディアの取扱いについての理解も深まりました。
また背景ぼかし機能に関しては今回次の実装ができなかったので、次回以降やってみたいと思います。
- デバイス準備画面でのプレビュー映像での背景ぼかしの利用
- 映像出力の開始を背景ぼかしを利用した状態で行う
参考
- videoタグのつまずき/スマホで再生されない/playsinlineの存在 | umlaut.club
- 動的にVideoタグを生成する - Qiita
- JavaScriptで自動再生のvideoを埋め込む時の注意点 | cly7796.net
- 【初心者向け】HTMLの一部を非表示にする方法 | CodeCampus
- /javascript
- ChildNode.remove() - Web API | MDN
- MediaDevices.getUserMedia() - Web API | MDN
- getUserMedia()で指定できるMediaTrackConstraintsのよもやま - console.lealog();
以上